<?php
/*
 * Just a note: Grader result
 * 
 * - P = pass
 * - T = time limit exceed
 * - X = memory limit exceed/harmful operation stopped
 * - - = wrong answer
 * - N = grader error
 */

define('START_TIME', time());

echo 'Evaluator starting on ' . gmdate(DATE_RFC822) . ".\n";

set_error_handler('error_handler');

function debug_print($msg) {
	//echo $msg;
}

function log_t($msg) {
	echo gmdate(DATE_RFC822) . ': ' . $msg . "\n";
}

function update_time() {
	global $conf;
	$filename = $conf['grader-time'];
	$content = time() . ':' . (time()-START_TIME);
	file_put_contents($filename, $content);
}

require dirname(__FILE__) . '/../../libraries/master.php';

update_time();

$Database = Database::getDatabase();

# Evaluator's autoloader
function evaluator_autoload($classname) {
	$file = EVALUATOR . '/classes/class.' . strtolower($classname) . '.php';
	if (file_exists($file)) {
		require $file;
		return true;
	}
	return false;
}

spl_autoload_register('evaluator_autoload');

# Change working directory to the temp folder.
set_bin_dir();


$lastCacheUpdateTime = time();
$lastDayTime = time();

# Main loop
while (true) {
	# Check for kill signal
	if (file_exists($conf['php-sigkill'])) {
		log_t ('Killing signal detected.');
		break;
	}
	
	# Update cache every ten seconds
	$time = time();
	if ($time - $lastCacheUpdateTime > 10) {
		TaskModel::buildCache();
		$lastCacheUpdateTime = $time;
		$Database->queries = array();
		
		// also update time
		update_time();
	}
	
	# Update every hours
	if ($time-$lastDayTime > 3600) {
		$diff = $time-START_TIME;
		$day = floor($diff/86400);
		$diff = $diff % 86400;
		$hour = floor($diff/3600);
		$diff = $diff % 3600;
		$minute = floor($diff/60);
		$diff = $diff % 60;
		log_t('Evaluator has been run for ' . "$day day(s) $hour hour(s) $minute minute(s) and $diff second(s)");
		$lastDayTime = $time;
	}
	
	$a = $Database->query('SELECT `sid`, `uid` FROM `submissions` WHERE `status` = :status', array('status' => IN_QUEUE));
	
	if ($Database->hasRows($a)) {
		$row = $Database->getRow();
		$id = $row['sid'];
		$uid = $row['uid'];
		
		log_t("Grading submission $id.");
		
		$submission = new SubmissionObject($id);
		$taskM = new TaskModel();
		
		$submission->status = COMPILING;
		$submission->update();
		
		$task = $taskM->getTask($submission->task);
		log_t(": Task: {$submission->task}");
		
		if ($task == false || intval($task['conf']['testcase']) <= 0) {
			$submission->status = GRADER_ERROR;
			$submission->update();
			continue;
		}
		
		$source = SUBMIT . '/' . $submission->id . '.source';
		$target = TEMP_BIN . '/' . $submission->id;
		
		# Compile submission
		$compiler = new Compiler($source, $target, $submission->lang, $conf['compile'][$submission->lang]);
		
		$result = $compiler->compile();
		$submission->compile_message = $compiler->output;
		
		if ($result == false) {
			// compile error
			$submission->status = COMPILE_ERROR;
			$submission->update();
			log_t(": Compilation error.");
		} else {
			// compile success
			$submission->status = GRADING;
			$submission->update();
			
			$task_dir = $task['path'];
			
			$stdin  = TEMP_BIN . '/.stdin';
			$stdout = TEMP_BIN . '/.stdout';
			$stderr = TEMP_BIN . '/.stderr';
			$stdsol = TEMP_BIN . '/.solution';
			
			$task_meta = array();
			
			for ($i = 1; $i <= intval($task['conf']['testcase']); $i++) {
				if (file_exists($stdin))  unlink($stdin);
				if (file_exists($stdout)) unlink($stdout);
				if (file_exists($stderr)) unlink($stderr);
				if (file_exists($stdsol)) unlink($stdsol);
				
				$in_file  = $task_dir . '/' . $i . '.in';
				$out_file = $task_dir . '/' . $i . '.out';
				$out_alt1 = $task_dir . '/' . $i . '.sol';
				$out_alt2 = $task_dir . '/' . $i . '.ans';
				
				$error = 0;
				if (file_exists($in_file)) {
					copy($in_file,  $stdin);
					++$error;
				}
				if (file_exists($out_file)) {
					copy($out_file, $stdsol);
					++$error;
				} elseif (file_exists($out_alt1)) {
					copy($out_alt1, $stdsol);
					++$error;
				} elseif (file_exists($out_alt2)) {
					copy($out_alt2, $stdsol);
					++$error;
				}
				
				// TODO check error
				if ($error != 2) {
					log_t (": ERROR: #$i test case of problem {$submission->task} is having problem : either out or in file is missing!");
				}
			
				$sandbox = new Sandbox($target);
				
				# Default limit
				$default_mem = 128*1024; // 128 MB;
				$default_time = 1;
				$default_file = 2; // Normal path
				
				
				$sandbox->setMemoryLimit($task['conf']['memory']);
				$sandbox->setTimeLimit($task['conf']['time']);
				$sandbox->addAllowPath('/dev/urandom');
				$sandbox->setFileLimit($task['conf']['file']);
				$sandbox->redirectInput($stdin);
				$sandbox->redirectOutput($stdout);
				$sandbox->redirectError($stderr);
				
				$sandbox->execute();
					
				$meta = array();
				$meta['out']   = file_exists($stdout) ? file_get_contents($stdout) : '';
				$meta['error'] = file_exists($stderr) ? file_get_contents($stderr) : '';
				$meta['meta']  = $sandbox->meta;
				
				if ($sandbox->status == 0) {
				
					$time = $sandbox->time;
					$mem  = $sandbox->mem;
					$meta['time'] = $time;
					$meta['mem'] = $mem;
					
					$status = isset($sandbox->meta['status']) ? $sandbox->meta['status'] : 'PP';
					
					$exitsig = isset($sandbox->meta['exitsig']) ? $sandbox->meta['exitsig'] : 0;
					
					if ($exitsig != 0) {
						// return other than zero, just in case.
						$meta['result'] = 'X';
						$status = 'SG';
					}
					
					switch ($status) {
						case 'XX': // sandbox error
							$meta['result'] = 'N';
							break;
						case 'TO': // Time limit Exceed
							$meta['result'] = 'T';
							break;
						case 'FA': // file access
						case 'SG': // suicide
						case 'RE': // idk
						case 'FO': // Unknown system call
							$meta['result'] = 'X';
							break;
						default:;
					}
					
					if ($status == 'PP') {
						// run correctly, check answer
						$diff = new Diff($stdout, $stdsol);
						if ($diff->same) {
							// correct
							$meta['result'] = 'P';
						} else {
							// incorrect
							$meta['result'] = '-';
						}
					}
				} else {
					// sandbox error
					$meta['result'] = 'N';
				}
				
				$task_meta[] = $meta;
			}
			
			// Summarizing result
			$numtestcases = intval($task['conf']['testcase']);
			$passed = 0;
			$result = '';
			$mem = 0;
			$usage = 0.0;
			
			foreach ($task_meta as $meta) {
				$result .= $meta['result'];
				if ($meta['result'] == 'P') $passed++;
				$mem = max($mem, isset($meta['mem']) ? $meta['mem'] : 0);
				$usage = max($usage, isset($meta['time']) ? $meta['time'] : 0);
			}
			
			if ($passed == $numtestcases) {
				$submission->status = PASSED;
			} else {
				$submission->status = FAILED;
			}
			$submission->result = $result;
			$submission->score = $passed/$numtestcases;
			$submission->task_metadata = serialize($task_meta);
			$submission->usage = $usage;
			$submission->mem = $mem;
			$submission->update();
			
			log_t (': Result: ' . $result);
			
			$usertask = new UserTask($uid, $submission->task);
			$exp = 0;
			if ($passed == $numtestcases) {
				$exp = $task['conf']['exp'];
			}
			$score = $task['conf']['score'] * $submission->score;
			$usertask->update_score($score, $exp);
			
		} // end check for compile error
		
		@unlink($target);
		
	} else {
		# Throttle to prevent too much MySQL server load
		usleep($conf['evaluator-throttle']);
	}
	
	# Free MySQL resource
	mysql_free_result($a);
}

log_t('Evaluator is shutting down...');

# Unlink the signal
unlink($conf['php-sigkill']);
# End
exit;

// Set up working directory
function set_temp_dir() {
	chdir(TEMP_);
}

function set_bin_dir() {
	chdir(TEMP_BIN);
}

// Error handler
function error_handler($errno, $errstr, $errfile, $errline) {
	switch ($errno) {
		case E_WARNING:
			log_t("Warning: [$errno] $errstr in file $errfile on line $errline.");
			break;
		case E_NOTICE:
			log_t("Notice: [$errno] $errstr in file $errfile on line $errline.");
			break;
		case E_STRICT:
			log_t("Strict standard: [$errno] $errstr in file $errfile on line $errline.");
			break;
		case E_RECOVERABLE_ERROR:
			log_t("Fatal error: [$errno] $errstr in file $errfile on line $errline.");
			break;
		default:
			log_t("Error: [$errno] $errstr in file $errfile on line $errline.");
			break;
    }
	return true;
}

